/*
 * @(#)CIEXYChromaticityDiagramImageProducer.java
 * 
 * Copyright (c) 2010 by the original authors of JHotDraw and all its
 * contributors. All rights reserved.
 * 
 * You may not use, copy or modify this file, except in compliance with the 
 * license agreement you entered into with the copyright holders. For details
 * see accompanying license terms.
 */
package org.jhotdraw.color;

import java.awt.Color;
import java.awt.Point;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.util.Arrays;

/**
 * Produces a CIE xy Chromaticity Diagram.
 * <p>
 * The diagram shows a projection of the CIE XYZ cube on a xy plane. The
 * projection is based on the following equations:
 * <p>
 * x = X / (X + Y + Z), y = Y / (X + Y + Z), z = 1 - x - y.
 * </p>
 * The equations can be rewritten as:
 * <p>
 * X = (x*(Y+Z)/(1-x), Y = (y*(X+Z)/(1-y).
 * 
 * 
 * @author Werner Randelshofer
 * @version $Id: CIEXYChromaticityDiagramImageProducer.java 717 2010-11-21
 *          12:30:57Z rawcoder $
 */
public class CIEXYChromaticityDiagramImageProducer extends MemoryImageSource {

	private final static float eps = 0f;// 0.000001f;
	private final static float ceps = 0f;
	protected int[] pixels;
	protected int w, h;
	protected ColorSpace colorSpace;
	protected int radialIndex = 1;
	protected int angularIndex = 0;
	protected int verticalIndex = 2;
	protected boolean isPixelsValid = false;
	protected float verticalValue = 1f;
	protected boolean isLookupValid = false;

	public enum OutsideGamutHandling {

		CLAMP, LEAVE_OUTSIDE
	};

	/** By default, clamps non-displayable RGB values. */
	private OutsideGamutHandling outsideGamutHandling = OutsideGamutHandling.LEAVE_OUTSIDE;

	public CIEXYChromaticityDiagramImageProducer(int w, int h) {
		super(w, h, null, 0, w);
		this.colorSpace = ICC_ColorSpace.getInstance(ICC_ColorSpace.CS_CIEXYZ);
		pixels = new int[w * h];
		this.w = w;
		this.h = h;
		setAnimated(true);

		newPixels(pixels, ColorModel.getRGBdefault(), 0, w);
	}

	public boolean needsGeneration() {
		return !isPixelsValid;
	}

	public void regenerateDiagram() {
		if (!isPixelsValid) {
			generateImage();
		}
	}

	public void generateImage() {
		float wf = 0.8f / (float) w;
		float hf = 0.9f / (float) h;
		// float wf = 1f / (float) w;
		// float hf = 1f / (float) h;

		// Clear pixels
		Arrays.fill(pixels, 0);
		float[] rgb = new float[3];
		for (int iY = 0; iY <= 100; iY++) {
			float Y = (100 - iY) / 100f;
			float[] XYZ = new float[3];
			for (int ix = 0; ix < w; ix++) {
				float x = ix * wf;
				for (int iy = 0; iy < h; iy++) {
					if (pixels[ix + iy * w] != 0) {
						continue;
					}

					float y = 0.9f - iy * hf;
					float z = 1f - x - y;

					if (y == 0) {
						XYZ[0] = XYZ[1] = XYZ[2] = 0;
					} else {
						XYZ[1] = Y; // Y=Y
						XYZ[0] = x * XYZ[1] / y; // X=x*Y/y
						XYZ[2] = z * XYZ[1] / y; // Z = (1-x-y)*Y/y
					}
					int alpha = XYZ[0] >= ceps && XYZ[1] >= ceps && XYZ[2] >= ceps && XYZ[0] <= 1 - ceps && XYZ[1] <= 1 - ceps
							&& XYZ[2] <= 1 - ceps ? 255 : 0;
					if (alpha == 255) {
						// rgb = colorSpace.toRGB(XYZ);
						// toRGB(XYZ,rgb);
						toRGB(XYZ, rgb);
						alpha = (rgb[0] >= eps && rgb[1] >= eps && rgb[2] >= eps && rgb[0] <= 1 - eps && rgb[1] <= 1 - eps && rgb[2] <= 1 - eps) //
						? 255
								: 0;
						if (alpha == 255) {
							// rgb = colorSpace.toRGB(XYZ);
							// pixels[ix + iy * w] = (alpha << 24) | ((int)
							// (rgb[0] * 255f) << 16) | ((int) (rgb[1] * 255f)
							// << 8) | (int) (rgb[2] * 255f);
							pixels[ix + iy * w] = (alpha << 24) | ((0xff & (int) (rgb[0] * 255f)) << 16)
									| ((0xff & (int) (rgb[1] * 255f)) << 8) | (0xff & (int) (rgb[2] * 255f));
						}
					}

				}
			}
		}
	}

	public Point getColorLocation(Color c) {
		float[] components = ColorUtil.fromColor(colorSpace, c);
		return getColorLocation(components);
	}

	public Point getColorLocation(float[] components) {
		return null;
	}

	public float[] getColorAt(int x, int y) {
		return null;
	}

	public int getWidth() {
		return w;
	}

	public int getHeight() {
		return h;
	}

	public void toRGB(float[] ciexyz, float[] rgb) {
		double X = ciexyz[0];
		double Y = ciexyz[1];
		double Z = ciexyz[2];

		// sRGB conversion
		// Convert to sRGB as described in
		// http://www.w3.org/Graphics/Color/sRGB.html
		/*
		 * double Rs = 3.2410 * X + -1.5374 * Y + -0.4986 * Z; double Gs =
		 * -0.9692 * X + 1.8760 * Y + 0.0416 * Z; double Bs = 0.0556 * X +
		 * -0.2040 * Y + 1.0570 * Z; /* / // Convert to sRGB as described in //
		 * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
		 * double Rs = 3.2404542 * X + -1.5371385 * Y + -0.4985314 * Z; double
		 * Gs = -0.9692660 * X + 1.8760108 * Y + 0.0415560 * Z; double Bs =
		 * 0.0556434 * X + -0.2040259 * Y + 1.0572252 * Z; /* / // proPhoto RGB
		 * conversion
		 * http://www.colour.org/tc8-05/Docs/colorspace/PICS2000_RIMM-ROMM.pdf
		 * double Rs = 1.3460 * X + -0.2556 * Y + -0.0511 * Z; double Gs =
		 * -0.5446 * X + 1.5082 * Y + 0.0205 * Z; double Bs = 0.0 * X + 0.0 * Y
		 * + 1.2123 * Z; / // One to one 'conversion' double Rs = 1 * X + 0 * Y
		 * + 0 * Z; double Gs = 0 * X + 1 * Y + 0 * Z; double Bs = 0 * X + 0 * Y
		 * + 1 * Z;
		 */
		// Convert to Wide Gamut RGB as described in
		// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
		double Rs = 1.4628067 * X + -0.1840623 * Y + -0.2743606 * Z;
		double Gs = -0.5217933 * X + 1.4472381 * Y + 0.0677227 * Z;
		double Bs = 0.0349342 * X + -0.0968930 * Y + 1.2884099 * Z;

		if (Rs <= 0.00304) {
			Rs = 12.92 * Rs;
		} else {
			Rs = 1.055 * Math.pow(Rs, 1 / 2.4) - 0.055;
		}
		if (Gs <= 0.00304) {
			Gs = 12.92 * Gs;
		} else {
			Gs = 1.055 * Math.pow(Gs, 1 / 2.4) - 0.055;
		}
		if (Bs <= 0.00304) {
			Bs = 12.92 * Bs;
		} else {
			Bs = 1.055 * Math.pow(Bs, 1 / 2.4) - 0.055;
		}

		switch (outsideGamutHandling) {
		case CLAMP:
			Rs = Math.min(1, Math.max(0, Rs));
			Gs = Math.min(1, Math.max(0, Gs));
			Bs = Math.min(1, Math.max(0, Bs));
			break;
		}

		rgb[0] = (float) Rs;
		rgb[1] = (float) Gs;
		rgb[2] = (float) Bs;

		// return new float[]{(float) Rs, (float) Gs, (float) Bs};
		// return sRGB.fromCIEXYZ(ciexyz);
	}
}
